<template>
  <view class="accurate-time-picker">
    <view class="picker-unit">
      <view class="picker-item-unit">{{ $t('Appliance.Settings.TimePicker.Unit.Hour.Text') }}</view>
      <view class="picker-item-unit">{{ $t('Appliance.Settings.TimePicker.Unit.Minute.Text') }}</view>
      <view class="picker-item-unit">{{ $t('Appliance.Settings.TimePicker.Unit.Second.Text') }}</view>
    </view>

    <picker-view
      class="picker-view"
      mask-class="picker-mask"
      indicator-class="picker-indicator"
      :value="timeIndex"
      :style="pickerViewStyle"
      @change="onTimeChange"
    >
      <picker-view-column class="picker-column hour-column">
        <view
          class="picker-item"
          v-for="(hour, index) in pickerHours"
          :key="index"
          :class="{ active: hourIndex === index }"
          :style="{ ...itemStyle, ...(hourIndex === index && itemActiveStyle) }"
        >
          <view class="text">{{ hour.label }}</view>
        </view>
      </picker-view-column>
      <picker-view-column class="picker-column minute-column">
        <view
          class="picker-item"
          v-for="(minute, index) in pickerMinutes"
          :key="index"
          :class="{ active: minuteIndex === index }"
          :style="{ ...itemStyle, ...(minuteIndex === index && itemActiveStyle) }"
        >
          <view class="text">{{ minute.label }}</view>
        </view>
      </picker-view-column>
      <picker-view-column class="picker-column second-column">
        <view
          class="picker-item"
          v-for="(second, index) in pickerSeconds"
          :key="index"
          :class="{ active: secondIndex === index }"
          :style="{ ...itemStyle, ...(secondIndex === index && itemActiveStyle) }"
        >
          <view class="text">{{ second.label }}</view>
        </view>
      </picker-view-column>
    </picker-view>
  </view>
</template>

<script setup lang="ts">
import dayjs from 'dayjs'
import type { CSSProperties } from 'vue'

export type TimeOption = {
  label: string // 字符格式
  value: number // 数字格式
}
// export type OrderHour = TimeOption & {
//   addDays: number // 往后追加的天数 (默认0天)
//   beginMinute?: number // 起始分钟 (默认0分)
//   endMinute?: number // 截至分钟 (默认59分)
// }
export type ChangePayload = {
  totalSeconds: number
}

const props = defineProps<{
  value: number
  min: number
  max: number
  stepsize: number
  immediate?: boolean
  pickerStyle?: CSSProperties
  itemStyle?: CSSProperties
  itemActiveStyle?: CSSProperties
}>()
const emit = defineEmits<{
  (e: 'update:value', value: number): void
  (e: 'change', payload: ChangePayload): void
}>()
onBeforeMount(() => init())

// 样式
const pickerViewStyle = computed<CSSProperties>(() => ({
  height: '226rpx',
  ...props.pickerStyle,
}))

// 时间
const pickerHours = ref<TimeOption[]>([])
const pickerMinutes = ref<TimeOption[]>([])
const pickerSeconds = ref<TimeOption[]>([])
const hourIndex = ref(0)
const minuteIndex = ref(0)
const secondIndex = ref(0)
const timeIndex = ref([0, 0, 0])
watch(
  () => [hourIndex.value, minuteIndex.value, secondIndex.value],
  async ([hourIndex, minuteIndex, secondIndex]) => {
    await nextTickPromise(10)
    timeIndex.value[0] = hourIndex
    await nextTickPromise(10)
    timeIndex.value[1] = minuteIndex
    await nextTickPromise(10)
    timeIndex.value[2] = secondIndex
  }
)

function init() {
  const currentTime = dayjs().hour(0).minute(0).second(props.value)

  /**
   * 重置小时
   */
  pickerHours.value = getHourList()
  hourIndex.value = getMostMatchedIndex(pickerHours.value, currentTime.hour())
  /**
   * 重置分钟
   */
  pickerMinutes.value = getMinuteList()
  minuteIndex.value = getMostMatchedIndex(pickerMinutes.value, currentTime.minute())
  /**
   * 重置秒钟
   */
  pickerSeconds.value = getSecondList()
  secondIndex.value = getMostMatchedIndex(pickerSeconds.value, currentTime.second())
  /**
   * 立即触发
   */
  props.immediate && triggerTimeChange()
}

async function onTimeChange(e: WechatMiniprogram.PickerChange) {
  let [newHourIndex, newMinuteIndex, newSecondIndex] = e.detail.value as [number, number, number]
  // 判断是否修改 小时、分钟
  let isHourChanged = newHourIndex !== hourIndex.value
  let isMinuteChanged = newMinuteIndex !== minuteIndex.value

  // 记录修改前的数值
  // const oldHourOption = pickerHours.value[hourIndex.value]
  const oldMinuteOption = pickerMinutes.value[minuteIndex.value]
  const oldSecondOption = pickerSeconds.value[secondIndex.value]

  // 修改小时 重新计算分钟
  if (isHourChanged) {
    /**
     * 重置分钟
     */
    pickerMinutes.value = getMinuteList(newHourIndex)
    // 标记已修改分钟 用于重新计算秒钟
    isMinuteChanged = true
    // 计算最匹配的下标
    newMinuteIndex = getMostMatchedIndex(pickerMinutes.value, oldMinuteOption.value)
  }

  // 修改分钟 重新计算秒钟
  if (isMinuteChanged) {
    /**
     * 重置秒钟
     */
    pickerSeconds.value = getSecondList(newHourIndex, newMinuteIndex)
    // 计算最匹配的下标
    newSecondIndex = getMostMatchedIndex(pickerSeconds.value, oldSecondOption.value)
  }

  // 重新设置下标
  hourIndex.value = newHourIndex
  minuteIndex.value = newMinuteIndex
  secondIndex.value = newSecondIndex

  // 触发change事件
  triggerTimeChange()
}

/**
 * 根据旧值与新选项 找出匹配的下标
 *  - 旧值在新选项范围内 返回新选项中匹配的下标
 *  - 小于新选项最小值 返回0
 *  - 大于新选项最大值 返回最后一条下标
 */
function getMostMatchedIndex(newOptions: TimeOption[], oldValue: number): number {
  // 计算后的最小最大分钟数
  const maxIndex = newOptions.length - 1
  const minOption = newOptions[0]
  const maxOption = newOptions[maxIndex]

  // 匹配的下标
  let matchedIndex!: number

  // 小于新选项最小值 取0
  if (oldValue < minOption.value) {
    matchedIndex = 0
  }
  // 大于新选项最大值 取最后一条下标
  else if (oldValue > maxOption.value) {
    matchedIndex = maxIndex
  }
  // 旧值在新选项范围内
  else {
    const newOptionIndex = newOptions.findIndex((option) => option.value === oldValue)
    // 防止出现找不到值 index为-1的情况 为-1时取0
    matchedIndex = Math.max(newOptionIndex, 0)
  }

  return matchedIndex
}

function triggerTimeChange() {
  const hourOption = pickerHours.value[hourIndex.value]
  const minuteOption = pickerMinutes.value[minuteIndex.value]
  const secondOption = pickerSeconds.value[secondIndex.value]

  const totalSeconds = hourOption.value * 3600 + minuteOption.value * 60 + secondOption.value
  emit('change', { totalSeconds })
  emit('update:value', totalSeconds)
}

function getHourList(): TimeOption[] {
  const hourList: TimeOption[] = []

  // 计算最小、最大的小时数
  const minHour = Math.floor(props.min / 3600)
  const maxHour = Math.floor(props.max / 3600)
  let stepsize = Math.floor(props.stepsize / 3600)
  stepsize = Math.max(stepsize, 1) // 最小1 防止死循环

  // 填充小时选项
  for (let hour = minHour; hour <= maxHour; hour += stepsize) {
    hourList.push({
      label: String(hour).padStart(2, '0'),
      value: hour,
    })
  }
  return hourList
}

function getMinuteList(selectedHourIndex: number = hourIndex.value): TimeOption[] {
  const minuteList: TimeOption[] = []

  // 当前小时数
  const currentHour = pickerHours.value[selectedHourIndex]?.value || 0

  // 计算最小的分钟数
  let minMinute = Math.floor((props.min - currentHour * 3600) / 60)
  minMinute = Math.max(minMinute, 0) // 最小限制0
  // 计算最大的分钟数
  let maxMinute = Math.floor((props.max - currentHour * 3600) / 60)
  maxMinute = Math.min(maxMinute, 59) // 最大限制59
  // 计算
  let stepsize = Math.floor(props.stepsize / 60)
  stepsize = Math.max(stepsize, 1) // 最小1 防止死循环

  // 填充分钟选项
  for (let minute = minMinute; minute <= maxMinute; minute += stepsize) {
    minuteList.push({
      label: String(minute).padStart(2, '0'),
      value: minute,
    })
  }
  return minuteList
}

function getSecondList(
  selectedHourIndex: number = hourIndex.value,
  selectedMinuteIndex: number = minuteIndex.value
): TimeOption[] {
  const secondList: TimeOption[] = []

  // 当前小时、分钟数
  const currentHour = pickerHours.value[selectedHourIndex]?.value || 0
  const currentMinute = pickerMinutes.value[selectedMinuteIndex]?.value || 0

  // 计算最小的分钟数
  let minSecond = props.min - currentHour * 3600 - currentMinute * 60
  minSecond = Math.max(minSecond, 0) // 最小限制0
  // 计算最大的分钟数
  let maxSecond = props.max - currentHour * 3600 - currentMinute * 60
  maxSecond = Math.min(maxSecond, 59) // 最大限制59
  // 计算
  const stepsize = Math.max(props.stepsize, 1) // 最小1 防止死循环

  // 填充分钟选项
  for (let second = minSecond; second <= maxSecond; second += stepsize) {
    secondList.push({
      label: String(second).padStart(2, '0'),
      value: second,
    })
  }
  return secondList
}

function nextTickPromise(delay: number) {
  return new Promise<void>((resolve) => {
    nextTick(() => {
      setTimeout(() => resolve(), delay)
    })
  })
}

defineExpose({
  init,
})
</script>

<style lang="less">
.accurate-time-picker {
  position: relative;
  color: rgba(var(--dark-vector), 0.8);

  /**
   * 去除默认样式
   */
  .picker-mask {
    background: transparent;
    border: none;
  }
  .picker-indicator {
    height: 72rpx;
    &::before,
    &::after {
      border: none;
    }
  }

  .picker-item {
    display: flex;
    justify-content: center;
    align-items: center;
    font-weight: 400;
    font-size: 36rpx;
    line-height: 50rpx;
    transition: all 0.2s ease;
    position: relative;
    text-align: center;
    &.active {
      font-weight: 600;
      font-size: 48rpx;
      line-height: 68rpx;
      color: var(--primary-color);
    }
    > .text {
      text-align: center;
    }
  }
  .picker-column {
    &.align-left .picker-item {
      justify-content: flex-start;
    }
    &.align-right .picker-item {
      justify-content: flex-end;
    }
  }

  /**
   * column单位
   */
  .picker-unit {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: space-around;
    align-items: center;
    color: var(--primary-color);
    font-size: 24rpx;
    font-weight: 400;
  }
  .picker-item-unit {
    position: relative;
    left: 56rpx;
  }
}
</style>
